﻿using System;
using System.IO;
using System.Net.Sockets;

namespace Actor.Net.Network.Gate
{
    public enum ParseState : byte
    {
        OnLength = 0,
        OnCommand = 1,
        OnRpcId = 2,
        OnBody = 3,
    }

    //----------------------Socket包头----------------------
    //  |----------------|----------------|----------------|
    //      Length            command           rpc id     
    //      2byte              2byte            4byte       
    //------------------------------------------------------
    //  Length  包长度
    //  command 消息总线CMD
    //  rpc id  rpc消息唯一标识

    public class GatePacketParser
    {
        public const ushort LengthSize = sizeof(ushort);
        public const ushort CommandSize = sizeof(ushort);
        public const int RpcIdSize = sizeof(int);
        public const int HeadSize = LengthSize + CommandSize + RpcIdSize;

        public const int LengthOffset = 0;
        public const int CommandOffset = LengthSize;
        public const int RpcIdOffset = CommandOffset + CommandSize;

        public const int BufferLength = 8192;

        public int BodySize => this.length - HeadSize;
        public MemoryStream BodyBuffer { get; } = new MemoryStream(BufferLength);
        public MemoryStream HeadBuffer { get; } = new MemoryStream(HeadSize);

        private ushort length;
        private ushort command;
        private int rpcId;
        private bool isOk;
        private ParseState state;

        public bool Parse(byte[] bytes, ref int offset, ref int count)
        {
            bool finish = false;
            while (true)
            {
                if (finish)
                    break;

                switch (this.state)
                {
                    case ParseState.OnLength:
                        if(this.ParseLength(bytes, ref offset, ref count))
                        {
                            this.length = BitConverter.ToUInt16(HeadBuffer.GetBuffer(), 0);
                            this.state = ParseState.OnCommand;
                            //非法攻击小包
                            if(this.length < HeadSize)
                            {
                                throw new SocketException((int)SocketError.SocketError);
                            }
                        }
                        break;
                    case ParseState.OnCommand:
                        if(ParseCommand(bytes, ref offset, ref count))
                        {
                            this.command = BitConverter.ToUInt16(HeadBuffer.GetBuffer(), CommandOffset);
                            this.state = ParseState.OnRpcId;
                        }
                        break;
                    case ParseState.OnRpcId:
                        if(ParseRpcId(bytes, ref offset, ref count))
                        {
                            this.rpcId = BitConverter.ToInt32(HeadBuffer.GetBuffer(), RpcIdOffset);
                            this.state = ParseState.OnBody;
                        }
                        break;
                    case ParseState.OnBody:
                        if(ParseBody(bytes, ref offset, ref count))
                        {
                            isOk = true;
                            this.state = ParseState.OnLength;
                        }
                        break;
                }

                if (isOk)
                {
                    this.BodyBuffer.Seek(0, SeekOrigin.Begin);
                    this.BodyBuffer.SetLength(this.BodySize);

                    finish = true;
                }
                else if (count == 0)
                {
                    finish = true;
                }
            }
            return isOk;
        }

        public void Flush()
        {
            this.isOk = false;
            this.length = 0;
            this.command = 0;
            this.rpcId = 0;
            this.state = ParseState.OnLength;

            this.HeadBuffer.Seek(0, SeekOrigin.Begin);
            this.HeadBuffer.SetLength(0);

            this.BodyBuffer.Seek(0, SeekOrigin.Begin);
            this.BodyBuffer.SetLength(0);
        }

        public GateTransferContext GetTransferContext(GateChannelContext channelContext)
        {
            return new GateTransferContext
            {
                Command = this.command,
                RpcId = this.rpcId,
                ReceiveBodyStream = this.BodyBuffer,
                ChannelContext = channelContext,
            };
        }

        private bool ParseLength(byte[] bytes, ref int offset, ref int count)
        {
            var needSize = LengthSize - (int)HeadBuffer.Length;
            if (count >= needSize)
            {
                HeadBuffer.Write(bytes, offset, needSize);
                offset += needSize;
                count -= needSize;
            }
            else
            {
                HeadBuffer.Write(bytes, offset, count);
                offset += count;
                count -= count;
            }
            return (int)HeadBuffer.Length == LengthSize;
        }

        private bool ParseCommand(byte[] bytes, ref int offset, ref int count)
        {
            var needSize = LengthSize + CommandSize - (int)HeadBuffer.Length;
            if (count >= needSize)
            {
                HeadBuffer.Write(bytes, offset, needSize);
                offset += needSize;
                count -= needSize;
            }
            else
            {
                HeadBuffer.Write(bytes, offset, count);
                offset += count;
                count -= count;
            }
            return (int)HeadBuffer.Length == LengthSize + CommandSize;
        }

        private bool ParseRpcId(byte[] bytes, ref int offset, ref int count)
        {
            var needSize = LengthSize + CommandSize + RpcIdSize - (int)HeadBuffer.Length;
            if (count >= needSize)
            {
                HeadBuffer.Write(bytes, offset, needSize);
                offset += needSize;
                count -= needSize;
            }
            else
            {
                HeadBuffer.Write(bytes, offset, count);
                offset += count;
                count -= count;
            }
            return (int)HeadBuffer.Length == LengthSize + CommandSize + RpcIdSize;
        }

        private bool ParseBody(byte[] bytes, ref int offset, ref int count)
        {
            var bodySize = this.BodySize;
            var needSize = bodySize - (int)BodyBuffer.Length;
            if (count >= needSize)
            {
                BodyBuffer.Write(bytes, offset, needSize);
                offset += needSize;
                count -= needSize;
            }
            else
            {
                BodyBuffer.Write(bytes, offset, count);
                offset += count;
                count -= count;
            }
            return (int)BodyBuffer.Length == bodySize;
        }
    }
}
